﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using freetypeCLR;

namespace GE.Font
{
    public class FreetypeFont
    {
        struct ColoredChar
        {
            public char c;
            public Color c1, c2;
            public int percentTimesThousand;

            public ColoredChar(char c, int percentTimes, Color c1, Color c2)
            {
                this.c = c;
                this.c1 = c1;
                this.c2 = c2;
                this.percentTimesThousand = percentTimes;
            }
        }

        private freetype ft;

        private Dictionary<char, FreetypeChar> fontCache;
        private Dictionary<ColoredChar, FreetypeChar> coloredFontCache;

        private long font_size;

        private byte[] getByte(String path)
        {
            byte[] buffer = new byte[32768];
            using (FileStream fs = File.OpenRead(font_path))
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    while (true)
                    {
                        int read = fs.Read(buffer, 0, buffer.Length);
                        if (read <= 0)
                            return ms.ToArray();
                        ms.Write(buffer, 0, read);
                    }
                }
            }
        }

        private static GraphicsDevice gd;
        private static String font_path;
        private static Dictionary<long, FreetypeFont> fontMap = new Dictionary<long, FreetypeFont>();
 
        public static void Initialize(GraphicsDevice gd, String font_path)
        {
            FreetypeFont.gd = gd;
            FreetypeFont.font_path = font_path;
        }

        public static FreetypeFont getFont(int size)
        {
            if (!fontMap.ContainsKey(size))
            {
                fontMap[size] = new FreetypeFont(gd, font_path, size);
            }
            return fontMap[size];
        }

        public static void DrawString(SpriteBatch spriteBatch, String str, Vector2 pos, int size, Color color)
        {
            foreach (char ch in str)
            {
                FreetypeChar fc = FreetypeFont.getFont(size).getTexture(ch);
                spriteBatch.Draw(fc.tex, pos + new Vector2(fc.bitmap.bitmap_left, -fc.bitmap.bitmap_top), color);
                pos.X += fc.bitmap.advance_x >> 6;
                pos.Y += fc.bitmap.advance_y >> 6;
            }
        }
        public static void DrawString(SpriteBatch spriteBatch, String str, Vector2 pos, float theta, int size, Color color)
        {
            foreach (char ch in str)
            {
                FreetypeChar fc = FreetypeFont.getFont(size).getTexture(ch);
                spriteBatch.Draw(fc.tex, 
                    pos + new Vector2(fc.bitmap.bitmap_left, -fc.bitmap.bitmap_top),
                    null, color, theta, new Vector2(0, 0), 1.0f, SpriteEffects.None, 0);
                float orthoX = fc.bitmap.advance_x >> 6,
                    orthoY = fc.bitmap.advance_y >> 6;
                pos.X += (float)(Math.Cos(theta) * orthoX + Math.Sin(theta) * orthoY);
                pos.Y += (float)(Math.Cos(theta) * orthoY + Math.Sin(theta) * orthoX);
            }
        }


        public FreetypeFont(GraphicsDevice gd, String font_path, int font_size)
        {
            this.font_size = font_size;
            fontCache = new Dictionary<char, FreetypeChar>();
            coloredFontCache = new Dictionary<ColoredChar, FreetypeChar>();
            int error;

            ft = new freetype();
            error = ft.Init_FreeType();

            byte[] rawFont = getByte(font_path);

            error = ft.New_Memory_Face(rawFont, 0);
            error = ft.Set_Char_Size(font_size * 64, 0, 96, 96);
        }

        public FreetypeChar getTexture(char ch, float percent, Color top, Color bottom)
        {
            ColoredChar coloredChar = new ColoredChar(ch, (int)(percent*1000), top, bottom);

            if (coloredFontCache.ContainsKey(coloredChar))
            {
                return coloredFontCache[coloredChar];
            }

            int index = ft.Get_Char_Index((int)ch);
            ft.Load_Glyph(index, 8);
            BitMap bitmap = ft.Render_Glyph(0);
            Texture2D tex2d;

            if (bitmap.bitmap.Length != 0)
            {
                Color[] clr = new Color[bitmap.width * bitmap.height];
                for (long i = 0; i < bitmap.bitmap.Length; i++)
                {
                    if (i / bitmap.width < (bitmap.height * (1-percent)))
                    {
                        clr[i] = new Color(top.R, top.G, top.B, (byte)bitmap.bitmap[i]);
                    }
                    else
                    {
                        clr[i] = new Color(bottom.R, bottom.G, bottom.B, (byte)bitmap.bitmap[i]);
                    }
                }

                tex2d = new Texture2D(gd, bitmap.width, bitmap.height);
                tex2d.SetData<Color>(clr);
            }
            else
            {
                Color[] clr = new Color[1];
                clr[0] = new Color(0, 0, 0, 0);
                tex2d = new Texture2D(gd, 1, 1);
                tex2d.SetData<Color>(clr);
            }

            FreetypeChar tex = new FreetypeChar(bitmap, tex2d);
            coloredFontCache.Add(coloredChar, tex);

            if (coloredFontCache.Count > 1000)
                coloredFontCache.Clear();

            return tex;
        }

        public FreetypeChar getTexture(char ch)
        {
            if (fontCache.ContainsKey(ch))
            {
                return fontCache[ch];
            }

            int index = ft.Get_Char_Index((int)ch);
            ft.Load_Glyph(index, 8);
            BitMap bitmap = ft.Render_Glyph(0);
            Texture2D tex2d;

            if (bitmap.bitmap.Length != 0)
            {
                Color[] clr = new Color[bitmap.width * bitmap.height];
                for (long i = 0; i < bitmap.bitmap.Length; i++)
                {
                    clr[i] = new Color(255, 255, 255, (byte)bitmap.bitmap[i]);
                }

                tex2d = new Texture2D(gd, bitmap.width, bitmap.height);
                tex2d.SetData<Color>(clr);
            }
            else
            {
                Color[] clr = new Color[1];
                clr[0] = new Color(0, 0, 0, 0);
                tex2d = new Texture2D(gd, 1, 1);
                tex2d.SetData<Color>(clr);
            }

            FreetypeChar tex = new FreetypeChar(bitmap, tex2d);
            fontCache.Add(ch, tex);

            if (fontCache.Count > 1000)
                fontCache.Clear();

            return tex;
        }
    }

    public class FreetypeChar
    {
        public BitMap bitmap;
        public Texture2D tex;
        public FreetypeChar(BitMap bitmap, Texture2D tex)
        {
            this.bitmap = bitmap;
            this.tex = tex;
        }
    }
}
